
#include "OiPCH.hpp"
#include "OiDataViewer.hpp"

namespace Oi
{
    namespace Appl
    {
        class DataViewerImpl : public Impl<DataViewer>
        {
        public:
            void init();
            void exit();

        public:
            QtCharts::QLineSeries* _series;
            QList<QPointF> _seriesData = QList<QPointF>();
            QtCharts::QLineSeries* _maxLine;
            QtCharts::QLineSeries* _minLine;

            QtCharts::QLineSeries* _upperWarningLine;
            QtCharts::QLineSeries* _lowerWarningLine;

            QtCharts::QChart* _chart;
            QtCharts::QChartView* _chartView;

            QDateTime _minTime;
            QDateTime _maxTime;

            float _minValue = -1;
            float _maxValue = -1;
            float _upperWarning = -1;
            float _lowerWarning = -1;
            int _displayInterval;

            QLabel* _cplabel;
            QLabel* _cpklabel;

            QPoint _currentPos = QPoint(0, 0);
            
            QString _name;
        };

        DataViewer::DataViewer(QWidget* parent, QString name, int displayIntervalSeconds, float minValue, float maxValue, float upperWarning, float lowerWarning) : QDialog(parent, Qt::CustomizeWindowHint | Qt::WindowTitleHint)
        {
            OI_F(DataViewer);
            d->_minValue = minValue;
            d->_maxValue = maxValue;
            d->_upperWarning = upperWarning;
            d->_lowerWarning = lowerWarning;
            d->_displayInterval = displayIntervalSeconds;
            d->_name = name;

            d->_series = new QtCharts::QLineSeries();
            d->_chart = new QtCharts::QChart();
            d->_chart->legend()->hide();
            d->_chart->setTheme(QtCharts::QChart::ChartThemeDark);

            QLinearGradient backgroundGradient;
            backgroundGradient.setStart(QPointF(0, 0));
            backgroundGradient.setFinalStop(QPointF(0, 1));
            backgroundGradient.setColorAt(1.0, QColor::fromRgb(32, 32, 32));
            backgroundGradient.setCoordinateMode(QGradient::ObjectBoundingMode);
            d->_chart->setBackgroundBrush(backgroundGradient);
            d->_chart->setBackgroundVisible(true);
            d->_chart->setPlotAreaBackgroundBrush(backgroundGradient);
            d->_chart->setPlotAreaBackgroundVisible(true);

            d->_chart->layout()->setContentsMargins(0, 0, 0, 0);
            d->_chart->setBackgroundRoundness(0);
            d->_chart->addSeries(d->_series);

            QtCharts::QDateTimeAxis* axisX = new QtCharts::QDateTimeAxis;
            axisX->setTickCount(10);
            axisX->setFormat("mm:ss");
            axisX->setGridLineVisible(false);
            d->_chart->addAxis(axisX, Qt::AlignBottom);
            d->_series->attachAxis(axisX);

            QtCharts::QValueAxis* axisY = new  QtCharts::QValueAxis;
            axisY->setLabelFormat("%.2f");
            axisY->setGridLineVisible(false);
            d->_chart->addAxis(axisY, Qt::AlignLeft);
            d->_series->attachAxis(axisY);

            QPen seriesPen(Qt::red, 1);
            seriesPen.setCosmetic(true);
            d->_series->setPen(seriesPen);

            if (minValue < 0)
            {
                d->_minLine = NULL;
            }
            else
            {
                d->_minLine = new QtCharts::QLineSeries();
                d->_minLine->setPen(QPen(Qt::red));
                d->_chart->addSeries(d->_minLine);
                d->_minLine->attachAxis(axisY);
                d->_minLine->attachAxis(axisX);
            }

            if (maxValue < 0)
            {
                d->_maxLine = NULL;
            }
            else
            {
                d->_maxLine = new QtCharts::QLineSeries();
                d->_maxLine->setPen(QPen(Qt::red));
                d->_chart->addSeries(d->_maxLine);
                d->_maxLine->attachAxis(axisY);
                d->_maxLine->attachAxis(axisX);
            }

            if (upperWarning < 0)
            {
                d->_upperWarningLine = NULL;
            }
            else
            {
                d->_upperWarningLine = new QtCharts::QLineSeries();
                d->_upperWarningLine->setPen(QPen(Qt::yellow));
                d->_chart->addSeries(d->_upperWarningLine);
                d->_upperWarningLine->attachAxis(axisY);
                d->_upperWarningLine->attachAxis(axisX);
            }

            if (lowerWarning < 0)
            {
                d->_lowerWarningLine = NULL;
            }
            else
            {
                d->_lowerWarningLine = new QtCharts::QLineSeries();
                d->_lowerWarningLine->setPen(QPen(Qt::yellow));
                d->_chart->addSeries(d->_lowerWarningLine);
                d->_lowerWarningLine->attachAxis(axisY);
                d->_lowerWarningLine->attachAxis(axisX);
            }

            QDateTime now = QDateTime::currentDateTime();

            if (minValue < 0 || maxValue < 0)
            {
                d->_chart->axisY()->setRange(0, 1);
            }
            else
            {
                float offset = (d->_maxValue - d->_minValue) * 0.4;
                d->_chart->axisY()->setRange(d->_minValue - offset, d->_maxValue + offset);
            }

            d->_minTime = now;
            d->_maxTime = now;
            d->_chart->axisX()->setRange(now.addSecs(-displayIntervalSeconds), now.addSecs(2));

            d->_chartView = new QtCharts::QChartView(d->_chart);
            d->_chartView->setRenderHint(QPainter::Antialiasing);
            QVBoxLayout* layout = new QVBoxLayout(this);
            layout->setContentsMargins(2, 2, 2, 2);
            layout->setSpacing(0);
            layout->addWidget(d->_chartView);
            setWindowTitle(name);
            resize(480, 320);

            auto timer = new QTimer(this);
            timer->setSingleShot(false);
            QObject::connect(timer, &QTimer::timeout, this, &DataViewer::updateCpCpk);
            timer->start(1000);

            d->_cplabel = new QLabel(this);
            auto labelFont = d->_cplabel->font();
            labelFont.setPointSizeF(10);
            d->_cplabel->setFont(labelFont);
            d->_cplabel->setText("<i>Cp</i> : NA.");

            d->_cpklabel = new QLabel(this);
            d->_cpklabel->setFont(labelFont);
            d->_cpklabel->setText("<i>Cpk</i> : NA.");

        }

        DataViewer::~DataViewer()
        {
        }

        void DataViewer::updateData(double value)
        {
            OI_F(DataViewer);
            QDateTime now = QDateTime::currentDateTime();

            d->_seriesData.append(QPointF(now.toMSecsSinceEpoch(), value));
            while (!d->_seriesData.isEmpty() && d->_seriesData[0].x() < d->_minTime.toMSecsSinceEpoch())
            {
                d->_seriesData.pop_front();
            }

            if (isHidden())
            {
                return;
            }

            d->_series->replace(d->_seriesData);

            d->_maxTime = now.addSecs(2);
            d->_minTime = now.addSecs(-d->_displayInterval);
            d->_chart->axisX()->setRange(d->_minTime, d->_maxTime);

            if (d->_minLine)
            {
                d->_minLine->replace(QList<QPointF>({ QPointF(d->_minTime.toMSecsSinceEpoch(),d->_minValue), QPointF(d->_maxTime.toMSecsSinceEpoch(),d->_minValue) }));
            }

            if (d->_maxLine)
            {
                d->_maxLine->replace(QList<QPointF>({ QPointF(d->_minTime.toMSecsSinceEpoch(),d->_maxValue), QPointF(d->_maxTime.toMSecsSinceEpoch(),d->_maxValue) }));
            }

            if (d->_upperWarningLine)
            {
                d->_upperWarningLine->replace(QList<QPointF>({ QPointF(d->_minTime.toMSecsSinceEpoch(),d->_upperWarning), QPointF(d->_maxTime.toMSecsSinceEpoch(),d->_upperWarning) }));
            }

            if (d->_lowerWarningLine)
            {
                d->_lowerWarningLine->replace(QList<QPointF>({ QPointF(d->_minTime.toMSecsSinceEpoch(),d->_lowerWarning), QPointF(d->_maxTime.toMSecsSinceEpoch(),d->_lowerWarning) }));
            }

            if (d->_minValue < 0 || d->_maxValue < 0)
            {
                d->_chart->axisY()->setRange(value - 0.5, value + 0.5);
            }

            if (d->_minValue > 0 && d->_maxValue > 0)
            {
                if (value < d->_minValue || value > d->_maxValue)
                {
                    d->_series->setPen(QPen(Qt::red));
                }
                else if((d->_lowerWarning > 0 && d->_upperWarning > 0) && (value < d->_lowerWarning || value > d->_upperWarning))
                {
                    d->_series->setPen(QPen(Qt::yellow));
                }
                else
                {
                    d->_series->setPen(QPen(Qt::red));
                }
            }
        }

        void DataViewer::updateDisplayData(double value)
        {
            OI_F(DataViewer);
            setWindowTitle(QString(d->_name + ":%1").arg(QString::number(value, 'f', 2)));
            update();
        }

        void DataViewer::adjustLabels()
        {
            OI_F(DataViewer);
            int spacing = 25;

            d->_cplabel->adjustSize();
            d->_cpklabel->adjustSize();

            d->_cplabel->move(QPoint(this->width() - d->_cplabel->width() - spacing, d->_cplabel->height() / 2 + spacing));
            d->_cpklabel->move(QPoint(this->width() - d->_cpklabel->width() - spacing, d->_cpklabel->height() / 2 + spacing + d->_cplabel->height()));
        }

        void DataViewer::resizeEvent(QResizeEvent* e)
        {
            OI_F(DataViewer);
            adjustLabels();
            emit moved(pos());

            if (e->spontaneous())
            {
                emit settingChanged();
            }

            QDialog::resizeEvent(e);
        }

        void DataViewer::moveEvent(QMoveEvent* event)
        {
            OI_F(DataViewer);
            emit moved(pos());
            d->_currentPos = pos();

            if (event->spontaneous())
            {
                emit settingChanged();
            }

            QDialog::moveEvent(event);
        }

        void DataViewer::closeEvent(QCloseEvent* event)
        {
            OI_F(DataViewer);
            emit requestContextMenu(pos() + QPoint(width() - 15, 15));
            event->ignore();
        }

        void DataViewer::contextMenuEvent(QContextMenuEvent* e)
        {
            emit requestContextMenu(mapToGlobal(e->pos()));
        }

        void DataViewer::showEvent(QShowEvent* event)
        {
            OI_F(DataViewer);
            move(d->_currentPos);
            QDialog::showEvent(event);
        }

        void DataViewer::hideEvent(QHideEvent* event)
        {
            OI_F(DataViewer);
            d->_currentPos = pos();
            QDialog::hideEvent(event);
        }

        void DataViewer::updateCpCpk()
        {
            OI_F(DataViewer);

            if (isHidden())
            {
                return;
            }

            auto points = d->_seriesData;

            if (points.size() >= 30 && d->_minValue > 0 && d->_maxValue > 0)
            {
                double mean = 0;
                double stdev = 0;

                for (auto& point : points)
                {
                    mean = mean + point.y();
                }

                mean = mean / double(points.size());
                
                for (auto& point : points)
                {
                    stdev = stdev + (point.y() - mean) * (point.y() - mean);
                }

                stdev = std::sqrt(stdev / double(points.size()));

                double cp = (d->_maxValue - d->_minValue) / (6 * stdev);
                double cpk = std::min((mean - d->_minValue) / (3 * stdev), (d->_maxValue - mean) / (3 * stdev));
                d->_cplabel->setText(QString("<i>Cp</i> : %1").arg(QString::number(cp,'f',2)));
                d->_cpklabel->setText(QString("<i>Cpk</i> : %1").arg(QString::number(cpk,'f',2)));
            }
            else
            {
                d->_cplabel->setText("<i>Cp</i> : NA.");
                d->_cpklabel->setText("<i>Cpk</i> : NA.");
            }

            d->_cplabel->adjustSize();
            d->_cpklabel->adjustSize();
            adjustLabels();
        }


    }
}


